<!DOCTYPE html>



  


<html class="theme-next pisces use-motion" lang="zh-Hans">
<head><meta name="generator" content="Hexo 3.9.0">
  <meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<script src="//cdn.bootcss.com/pace/1.0.2/pace.min.js"></script>
<link href="//cdn.bootcss.com/pace/1.0.2/themes/pink/pace-theme-flash.css" rel="stylesheet">
<style>
    .pace .pace-progress {
        background: #1E92FB; /*进度条颜色*/
        height: 3px;
    }
    .pace .pace-progress-inner {
         box-shadow: 0 0 10px #1E92FB, 0 0 5px     #1E92FB; /*阴影颜色*/
    }
    .pace .pace-activity {
        border-top-color: #1E92FB;    /*上边框颜色*/
        border-left-color: #1E92FB;    /*左边框颜色*/
    }
</style>
<meta name="theme-color" content="#222">









<meta http-equiv="Cache-Control" content="no-transform">
<meta http-equiv="Cache-Control" content="no-siteapp">



  <meta name="google-site-verification" content="true">

<script data-ad-client="ca-pub-3331353808689126" async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>







  <meta name="baidu-site-verification" content="true">







  
  
  <link href="/lib/fancybox/source/jquery.fancybox.css?v=2.1.5" rel="stylesheet" type="text/css">




  
  
  
  

  
    
    
  

  
    
      
    

    
  

  

  
    
      
    

    
  

  
    
      
    

    
  

  
    
    
    <link href="https://fonts.cat.net/css?family=Lato:300,300italic,400,400italic,700,700italic|Roboto Slab:300,300italic,400,400italic,700,700italic|Lobster Two:300,300italic,400,400italic,700,700italic|PT Mono:300,300italic,400,400italic,700,700italic&subset=latin,latin-ext" rel="stylesheet" type="text/css">
  






<link href="/lib/font-awesome/css/font-awesome.min.css?v=4.6.2" rel="stylesheet" type="text/css">

<link href="/css/main.css?v=5.1.4" rel="stylesheet" type="text/css">


  <link rel="apple-touch-icon" sizes="180x180" href="/images/apple-touch-icon-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="32x32" href="/images/favicon-32x32-next.png?v=5.1.4">


  <link rel="icon" type="image/png" sizes="16x16" href="/images/favicon-16x16-next.png?v=5.1.4">


  <link rel="mask-icon" href="/images/logo.svg?v=5.1.4" color="#222">





  <meta name="keywords" content="缓存穿透,缓存雪崩,原理,redis,主从复制,">





  <link rel="alternate" href="/atom.xml" title="爱吃jelly的Jelly" type="application/atom+xml">






<meta name="description" content="Redis 是什么Redis: REmote DIctionary Server(远程字典服务器)完全开源免费，C语言编写遵守BSD协议，一个高性能的（key/value)分布式内存数据库。基于内存运行并支持持久化的NOSQL数据库被称为数据结构服务器。">
<meta name="keywords" content="缓存穿透,缓存雪崩,原理,redis,主从复制">
<meta property="og:type" content="article">
<meta property="og:title" content="Redis基础、原理全覆盖">
<meta property="og:url" content="https://jelly54.github.io/redis-learn/index.html">
<meta property="og:site_name" content="爱吃jelly的Jelly">
<meta property="og:description" content="Redis 是什么Redis: REmote DIctionary Server(远程字典服务器)完全开源免费，C语言编写遵守BSD协议，一个高性能的（key/value)分布式内存数据库。基于内存运行并支持持久化的NOSQL数据库被称为数据结构服务器。">
<meta property="og:locale" content="zh-Hans">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/27/lEOZA1.jpg">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/27/lEXuan.jpg">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/lezWdS.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/lmS6fJ.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/27/lVCzqK.jpg">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/leIvjJ.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/leoi4K.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/leoXIP.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/leTyJf.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/leTBdI.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/leTsFP.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/le7Emd.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/le7kOH.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/le7PSO.png">
<meta property="og:image" content="https://s2.ax1x.com/2019/12/28/le79fK.png">
<meta property="og:updated_time" content="2020-04-04T12:25:45.163Z">
<meta name="twitter:card" content="summary">
<meta name="twitter:title" content="Redis基础、原理全覆盖">
<meta name="twitter:description" content="Redis 是什么Redis: REmote DIctionary Server(远程字典服务器)完全开源免费，C语言编写遵守BSD协议，一个高性能的（key/value)分布式内存数据库。基于内存运行并支持持久化的NOSQL数据库被称为数据结构服务器。">
<meta name="twitter:image" content="https://s2.ax1x.com/2019/12/27/lEOZA1.jpg">



<script type="text/javascript" id="hexo.configurations">
  var NexT = window.NexT || {};
  var CONFIG = {
    root: '/',
    scheme: 'Pisces',
    version: '5.1.4',
    sidebar: {"position":"left","display":"post","offset":12,"b2t":false,"scrollpercent":false,"onmobile":false},
    fancybox: true,
    tabs: true,
    motion: {"enable":true,"async":false,"transition":{"post_block":"fadeIn","post_header":"slideDownIn","post_body":"slideDownIn","coll_header":"slideLeftIn","sidebar":"slideUpIn"}},
    duoshuo: {
      userId: '0',
      author: '博主'
    },
    algolia: {
      applicationID: '',
      apiKey: '',
      indexName: '',
      hits: {"per_page":10},
      labels: {"input_placeholder":"Search for Posts","hits_empty":"We didn't find any results for the search: ${query}","hits_stats":"${hits} results found in ${time} ms"}
    }
  };
</script>



  <link rel="canonical" href="https://jelly54.github.io/redis-learn/">






  <meta name="baidu-site-verification" content="t21ZdXzSqf">



<meta name="google-site-verification" content="VmrkZiYJtPrIIH8zlY82aG2_l1Ykrp3JZ3HQbMt0qCo">

  <title>Redis基础、原理全覆盖 | 爱吃jelly的Jelly</title>
  








</head>

<body itemscope itemtype="http://schema.org/WebPage" lang="zh-Hans">

  
  
    
  

  <div class="container sidebar-position-left page-post-detail">
    <div class="headband"></div>
    
    <a href="https://github.com/jelly54" class="github-corner" aria-label="View source on GitHub"><svg width="80" height="80" viewbox="0 0 250 250" style="fill:#151513; color:#fff; position: absolute; top: 0; border: 0; right: 0;" aria-hidden="true"><path d="M0,0 L115,115 L130,115 L142,142 L250,250 L250,0 Z"/><path d="M128.3,109.0 C113.8,99.7 119.0,89.6 119.0,89.6 C122.0,82.7 120.5,78.6 120.5,78.6 C119.2,72.0 123.4,76.3 123.4,76.3 C127.3,80.9 125.5,87.3 125.5,87.3 C122.9,97.6 130.6,101.9 134.4,103.2" fill="currentColor" style="transform-origin: 130px 106px;" class="octo-arm"/><path d="M115.0,115.0 C114.9,115.1 118.7,116.5 119.8,115.4 L133.7,101.6 C136.9,99.2 139.9,98.4 142.2,98.6 C133.8,88.0 127.5,74.4 143.8,58.0 C148.5,53.4 154.0,51.2 159.7,51.0 C160.3,49.4 163.2,43.6 171.4,40.1 C171.4,40.1 176.1,42.5 178.8,56.2 C183.1,58.6 187.2,61.8 190.9,65.4 C194.5,69.0 197.7,73.2 200.1,77.6 C213.8,80.2 216.3,84.9 216.3,84.9 C212.7,93.1 206.9,96.0 205.4,96.6 C205.1,102.4 203.0,107.8 198.3,112.5 C181.9,128.9 168.3,122.5 157.7,114.1 C157.9,116.9 156.7,120.9 152.7,124.9 L141.0,136.5 C139.8,137.7 141.6,141.9 141.8,141.8 Z" fill="currentColor" class="octo-body"/></svg></a><style>.github-corner:hover .octo-arm{animation:octocat-wave 560ms ease-in-out}@keyframes octocat-wave{0%,100%{transform:rotate(0)}20%,60%{transform:rotate(-25deg)}40%,80%{transform:rotate(10deg)}}@media (max-width:500px){.github-corner:hover .octo-arm{animation:none}.github-corner .octo-arm{animation:octocat-wave 560ms ease-in-out}}</style>

    <header id="header" class="header" itemscope itemtype="http://schema.org/WPHeader">
      <div class="header-inner"><div class="site-brand-wrapper">
  <div class="site-meta ">
    

    <div class="custom-logo-site-title">
      <a href="/" class="brand" rel="start">
        <span class="logo-line-before"><i></i></span>
        <span class="site-title">爱吃jelly的Jelly</span>
        <span class="logo-line-after"><i></i></span>
      </a>
    </div>
      
        <p class="site-subtitle">就揪啾  就是 Jelly</p>
      
  </div>

  <div class="site-nav-toggle">
    <button>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
      <span class="btn-bar"></span>
    </button>
  </div>
</div>

<nav class="site-nav">
  

  
    <ul id="menu" class="menu">
      
        
        <li class="menu-item menu-item-home">
          <a href="/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-home"></i> <br>
            
            首页
          </a>
        </li>
      
        
        <li class="menu-item menu-item-archives">
          <a href="/archives/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-archive"></i> <br>
            
            归档
          </a>
        </li>
      
        
        <li class="menu-item menu-item-categories">
          <a href="/categories/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-th"></i> <br>
            
            分类
          </a>
        </li>
      
        
        <li class="menu-item menu-item-tags">
          <a href="/tags/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-tags"></i> <br>
            
            标签
          </a>
        </li>
      
        
        <li class="menu-item menu-item-about">
          <a href="/about/" rel="section">
            
              <i class="menu-item-icon fa fa-fw fa-user"></i> <br>
            
            关于作者
          </a>
        </li>
      

      
        <li class="menu-item menu-item-search">
          
            <a href="javascript:;" class="popup-trigger">
          
            
              <i class="menu-item-icon fa fa-search fa-fw"></i> <br>
            
            搜索
          </a>
        </li>
      
    </ul>
  

  
    <div class="site-search">
      
  <div class="popup search-popup local-search-popup">
  <div class="local-search-header clearfix">
    <span class="search-icon">
      <i class="fa fa-search"></i>
    </span>
    <span class="popup-btn-close">
      <i class="fa fa-times-circle"></i>
    </span>
    <div class="local-search-input-wrapper">
      <input autocomplete="off" placeholder="搜索..." spellcheck="false" type="text" id="local-search-input">
    </div>
  </div>
  <div id="local-search-result"></div>
</div>



    </div>
  
</nav>



 </div>
    </header>

    <main id="main" class="main">
      <div class="main-inner">
        <div class="content-wrap">
          <div id="content" class="content">
            

  <div id="posts" class="posts-expand">
    

  

  
  
  

  <article class="post post-type-normal" itemscope itemtype="http://schema.org/Article">
  
  
  
  <div class="post-block">
    <link itemprop="mainEntityOfPage" href="https://jelly54.github.io/redis-learn/">

    <span hidden itemprop="author" itemscope itemtype="http://schema.org/Person">
      <meta itemprop="name" content="Big Jelly">
      <meta itemprop="description" content>
      <meta itemprop="image" content="/images/avatar.jpg">
    </span>

    <span hidden itemprop="publisher" itemscope itemtype="http://schema.org/Organization">
      <meta itemprop="name" content="爱吃jelly的Jelly">
    </span>

    
      <header class="post-header">

        
        
          <h1 class="post-title" itemprop="name headline">Redis基础、原理全覆盖</h1>
        

        <div class="post-meta">
          <span class="post-time">
            
              <span class="post-meta-item-icon">
                <i class="fa fa-calendar-o"></i>
              </span>
              
                <span class="post-meta-item-text">发表于</span>
              
              <time title="创建于" itemprop="dateCreated datePublished" datetime="2019-12-22T17:32:14+08:00">
                2019-12-22
              </time>
            

            

            
          </span>

          
            <span class="post-category">
            
              <span class="post-meta-divider">|</span>
            
              <span class="post-meta-item-icon">
                <i class="fa fa-folder-o"></i>
              </span>
              
                <span class="post-meta-item-text">分类于</span>
              
              
                <span itemprop="about" itemscope itemtype="http://schema.org/Thing">
                  <a href="/categories/Redis/" itemprop="url" rel="index">
                    <span itemprop="name">Redis</span>
                  </a>
                </span>

                
                
              
            </span>
          

          
            
          

          
          

          

          
            <div class="post-wordcount">
              
                
                <span class="post-meta-item-icon">
                  <i class="fa fa-file-word-o"></i>
                </span>
                
                  <span class="post-meta-item-text">字数统计&#58;</span>
                
                <span title="字数统计">
                  10.3k
                </span>
              

              
                <span class="post-meta-divider">|</span>
              

              
                <span class="post-meta-item-icon">
                  <i class="fa fa-clock-o"></i>
                </span>
                
                  <span class="post-meta-item-text">阅读时长 &asymp;</span>
                
                <span title="阅读时长">
                  38
                </span>
              
            </div>
          

          

        </div>
      </header>
    

    
    
    
    <div class="post-body" itemprop="articleBody">

      
      

      
        <h1 id="Redis-是什么"><a href="#Redis-是什么" class="headerlink" title="Redis 是什么"></a>Redis 是什么</h1><p>Redis: REmote DIctionary Server(远程字典服务器)<br>完全开源免费，C语言编写遵守BSD协议，一个高性能的（key/value)分布式内存数据库。基于内存运行并支持持久化的NOSQL数据库被称为数据结构服务器。</p>
<a id="more"></a>

<h2 id="与其他key-value缓存产品区别？"><a href="#与其他key-value缓存产品区别？" class="headerlink" title="与其他key-value缓存产品区别？"></a>与其他key-value缓存产品区别？</h2><ul>
<li>性能优秀，数据在内存中，读写速度非常快，支持并发- 10W QPS；</li>
<li>单进程单线程，是线程安全的，采用IO多路复用机制；</li>
<li>丰富的数据类型，支持字符串（strings）、散列（has- hes）、列表（lists）、集合（sets）、有序集合（so- rted sets）等；</li>
<li>支持数据持久化。可以将内存中数据保存在磁盘中，重- 启时加载；</li>
<li>主从复制，哨兵，高可用；</li>
<li>可以用作分布式锁；</li>
<li>可以作为消息中间件使用，支持发布订阅</li>
</ul>
<hr>
<h1 id="五种数据类型"><a href="#五种数据类型" class="headerlink" title="五种数据类型"></a>五种数据类型</h1><h2 id="Redis如何管理"><a href="#Redis如何管理" class="headerlink" title="Redis如何管理"></a>Redis如何管理</h2><p>先来了解下Redis内部内存管理是如何描述这5种数据类型。</p>
<p><img src="https://s2.ax1x.com/2019/12/27/lEOZA1.jpg" alt="lEOZA1.jpg"></p>
<p>首先redis内部使用一个redisObject对象来表示所有的key和value，redisObject最主要的信息如上图所示：<strong>type表示一个value对象具体是何种数据类型;<br>encoding是不同数据类型在redis内部的存储方式。</strong></p>
<p>比如：type=string表示value存储的是一个普通字符串，那么encoding可以是raw或者int。</p>
<h2 id="5种数据类型"><a href="#5种数据类型" class="headerlink" title="5种数据类型"></a>5种数据类型</h2><ol>
<li><p><strong>string是redis最基本的类型，可以理解成与memcached一模一样的类型，一个key对应一个value。</strong> value不仅是string，也可以是数字。string类型是二进制安全的，意思是redis的string类型可以包含任何数据，比如jpg图片或者序列化的对象。string类型的值最大能存储512M。</p>
</li>
<li><p><strong>Hash是一个键值（key-value）的集合。</strong> redis的hash是一个string的key和value的映射表，Hash特别适合存储对象。常用命令：hget,hset,hgetall等。</p>
</li>
<li><p><strong>list列表是简单的字符串列表，按照插入顺序排序。</strong> 可以添加一个元素到列表的头部（左边）或者尾部（右边） 常用命令：lpush、rpush、lpop、rpop、lrange(获取列表片段)等。</p>
<p> 应用场景：list应用场景非常多，也是Redis最重要的数据结构之一，比如twitter的关注列表，粉丝列表都可以用list结构来实现。</p>
<p> 数据结构：list就是链表，可以用来当消息队列用。redis提供了List的push和pop操作，还提供了操作某一段的api，可以直接查询或者删除某一段的元素。</p>
<p> 实现方式：redis <strong>list的是实现是一个双向链表，</strong> 既可以支持反向查找和遍历，更方便操作，不过带来了额外的内存开销。</p>
</li>
<li><p><strong>set是string类型的无序集合。</strong> 集合是通过hashtable实现的。set中的元素是没有顺序的，而且是没有重复的。</p>
<p> 常用命令：sdd、spop、smembers、sunion等。</p>
<p> 应用场景：redis set对外提供的功能和list一样是一个列表，特殊之处在于set是自动去重的，而且set提供了判断某个成员是否在一个set集合中。</p>
</li>
<li><p><strong>zset和set一样是string类型元素的集合，且不允许重复的元素。</strong> 常用命令：zadd、zrange、zrem、zcard等。</p>
<p> 使用场景：sorted set可以通过用户额外提供一个优先级（score）的参数来为成员排序，并且是插入有序的，即自动排序。当你需要一个有序的并且不重复的集合列表，那么可以选择sorted set结构。和set相比，sorted set关联了一个double类型权重的参数score，使得集合中的元素能够按照score进行有序排列，redis正是通过分数来为集合中的成员进行从小到大的排序。</p>
<p> 实现方式：Redis <strong>sorted set的内部使用HashMap和跳跃表(skipList)来保证数据的存储和有序，</strong> HashMap里放的是成员到score的映射，而跳跃表里存放的是所有的成员，排序依据是HashMap里存的score，使用跳跃表的结构可以获得比较高的查找效率，并且在实现上比较简单。</p>
</li>
</ol>
<h2 id="数据类型应用场景总结"><a href="#数据类型应用场景总结" class="headerlink" title="数据类型应用场景总结"></a>数据类型应用场景总结</h2><p><img src="https://s2.ax1x.com/2019/12/27/lEXuan.jpg" alt="lEXuan.jpg"></p>
<hr>
<h1 id="Redis缓存"><a href="#Redis缓存" class="headerlink" title="Redis缓存"></a>Redis缓存</h1><p>结合spring boot使用的。一般有两种方式，一种是直接通过RedisTemplate来使用，另一种是使用spring cache集成Redis（也就是注解的方式）</p>
<figure class="highlight xml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br></pre></td><td class="code"><pre><span class="line"><span class="tag">&lt;<span class="name">dependencies</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.apache.commons<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>commons-pool2<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-web<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.session<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-session-data-redis<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.projectlombok<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>lombok<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">optional</span>&gt;</span>true<span class="tag">&lt;/<span class="name">optional</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;<span class="name">dependency</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">groupId</span>&gt;</span>org.springframework.boot<span class="tag">&lt;/<span class="name">groupId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">artifactId</span>&gt;</span>spring-boot-starter-test<span class="tag">&lt;/<span class="name">artifactId</span>&gt;</span></span><br><span class="line">        <span class="tag">&lt;<span class="name">scope</span>&gt;</span>test<span class="tag">&lt;/<span class="name">scope</span>&gt;</span></span><br><span class="line">    <span class="tag">&lt;/<span class="name">dependency</span>&gt;</span></span><br><span class="line"><span class="tag">&lt;/<span class="name">dependencies</span>&gt;</span></span><br></pre></td></tr></table></figure>

<h2 id="Tips"><a href="#Tips" class="headerlink" title="Tips"></a>Tips</h2><ol>
<li>spring-boot-starter-data-redis: <strong>在spring boot 2.x以后底层不再使用Jedis，而是换成了Lettuce。</strong></li>
<li>commons-pool2：<strong>用作redis连接池，如不引入启动会报错</strong></li>
<li>spring-session-data-redis：spring session引入，用作共享session。</li>
</ol>
<p>配置文件application.yml的配置：</p>
<figure class="highlight yml"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br></pre></td><td class="code"><pre><span class="line"><span class="attr">server:</span></span><br><span class="line"><span class="attr">  port:</span> <span class="number">8082</span></span><br><span class="line"><span class="attr">  servlet:</span></span><br><span class="line"><span class="attr">    session:</span></span><br><span class="line"><span class="attr">      timeout:</span> <span class="number">30</span><span class="string">ms</span></span><br><span class="line"><span class="attr">spring:</span></span><br><span class="line"><span class="attr">  cache:</span></span><br><span class="line"><span class="attr">    type:</span> <span class="string">redis</span></span><br><span class="line"><span class="attr">  redis:</span></span><br><span class="line"><span class="attr">    host:</span> <span class="number">127.0</span><span class="number">.0</span><span class="number">.1</span></span><br><span class="line"><span class="attr">    port:</span> <span class="number">6379</span></span><br><span class="line"><span class="attr">    password:</span></span><br><span class="line">    <span class="comment"># redis默认情况下有16个分片，这里配置具体使用的分片，默认为0</span></span><br><span class="line"><span class="attr">    database:</span> <span class="number">0</span></span><br><span class="line"><span class="attr">    lettuce:</span></span><br><span class="line"><span class="attr">      pool:</span></span><br><span class="line">        <span class="comment"># 连接池最大连接数(使用负数表示没有限制),默认8</span></span><br><span class="line"><span class="attr">        max-active:</span> <span class="number">100</span></span><br></pre></td></tr></table></figure>

<h2 id="RedisTemplate使用方式"><a href="#RedisTemplate使用方式" class="headerlink" title="RedisTemplate使用方式"></a>RedisTemplate使用方式</h2><p>默认情况下的模板只能支持RedisTemplate&lt;String, String&gt;，也就是只能存入字符串，所以自定义模板很有必要。</p>
<p>添加配置类RedisCacheConfig.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Configuration</span></span><br><span class="line"><span class="meta">@AutoConfigureAfter</span>(RedisAutoConfiguration.class)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">RedisCacheConfig</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Bean</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> RedisTemplate&lt;String, Serializable&gt; <span class="title">redisCacheTemplate</span><span class="params">(LettuceConnectionFactory connectionFactory)</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">        RedisTemplate&lt;String, Serializable&gt; template = <span class="keyword">new</span> RedisTemplate&lt;&gt;();</span><br><span class="line">        template.setKeySerializer(<span class="keyword">new</span> StringRedisSerializer());</span><br><span class="line">        template.setValueSerializer(<span class="keyword">new</span> GenericJackson2JsonRedisSerializer());</span><br><span class="line">        template.setConnectionFactory(connectionFactory);</span><br><span class="line">        <span class="keyword">return</span> template;</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>测试类</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@RestController</span></span><br><span class="line"><span class="meta">@RequestMapping</span>(<span class="string">"/user"</span>)</span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserController</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Logger logger = LogManager.getLogger(UserController.class);</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> StringRedisTemplate stringRedisTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Autowired</span></span><br><span class="line">    <span class="keyword">private</span> RedisTemplate&lt;String, Serializable&gt; redisCacheTemplate;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@RequestMapping</span>(<span class="string">"/test"</span>)</span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">test</span><span class="params">()</span> </span>&#123;</span><br><span class="line">        redisCacheTemplate.opsForValue().set(<span class="string">"userkey"</span>, <span class="keyword">new</span> User(<span class="number">1</span>, <span class="string">"张三"</span>, <span class="number">25</span>));</span><br><span class="line">        User user = (User) redisCacheTemplate.opsForValue().get(<span class="string">"userkey"</span>);</span><br><span class="line">        logger.info(<span class="string">"当前获取对象：&#123;&#125;"</span>, user.toString());</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>


<h2 id="使用spring-cache集成redis"><a href="#使用spring-cache集成redis" class="headerlink" title="使用spring cache集成redis"></a>使用spring cache集成redis</h2><p>spring cache具备很好的灵活性，不仅能够使用<strong>SPEL(spring expression language)</strong> 来定义缓存的key和各种condition，还提供了开箱即用的缓存临时存储方案，也支持和主流的专业缓存如EhCache、Redis、Guava的集成。</p>
<p>定义接口UserService.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">interface</span> <span class="title">UserService</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function">User <span class="title">save</span><span class="params">(User user)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">void</span> <span class="title">delete</span><span class="params">(<span class="keyword">int</span> id)</span></span>;</span><br><span class="line"></span><br><span class="line">    <span class="function">User <span class="title">get</span><span class="params">(Integer id)</span></span>;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>接口实现类UserServiceImpl.java</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br><span class="line">28</span><br><span class="line">29</span><br><span class="line">30</span><br><span class="line">31</span><br><span class="line">32</span><br><span class="line">33</span><br><span class="line">34</span><br><span class="line">35</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@Service</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">UserServiceImpl</span> <span class="keyword">implements</span> <span class="title">UserService</span></span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">public</span> <span class="keyword">static</span> Logger logger = LogManager.getLogger(UserServiceImpl.class);</span><br><span class="line"></span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">static</span> Map&lt;Integer, User&gt; userMap = <span class="keyword">new</span> HashMap&lt;&gt;();</span><br><span class="line">    <span class="keyword">static</span> &#123;</span><br><span class="line">        userMap.put(<span class="number">1</span>, <span class="keyword">new</span> User(<span class="number">1</span>, <span class="string">"肖战"</span>, <span class="number">25</span>));</span><br><span class="line">        userMap.put(<span class="number">2</span>, <span class="keyword">new</span> User(<span class="number">2</span>, <span class="string">"王一博"</span>, <span class="number">26</span>));</span><br><span class="line">        userMap.put(<span class="number">3</span>, <span class="keyword">new</span> User(<span class="number">3</span>, <span class="string">"杨紫"</span>, <span class="number">24</span>));</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line"></span><br><span class="line">    <span class="meta">@CachePut</span>(value =<span class="string">"user"</span>, key = <span class="string">"#user.id"</span>)</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> User <span class="title">save</span><span class="params">(User user)</span> </span>&#123;</span><br><span class="line">        userMap.put(user.getId(), user);</span><br><span class="line">        logger.info(<span class="string">"进入save方法，当前存储对象：&#123;&#125;"</span>, user.toString());</span><br><span class="line">        <span class="keyword">return</span> user;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@CacheEvict</span>(value=<span class="string">"user"</span>, key = <span class="string">"#id"</span>)</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">void</span> <span class="title">delete</span><span class="params">(<span class="keyword">int</span> id)</span> </span>&#123;</span><br><span class="line">        userMap.remove(id);</span><br><span class="line">        logger.info(<span class="string">"进入delete方法，删除成功"</span>);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Cacheable</span>(value = <span class="string">"user"</span>, key = <span class="string">"#id"</span>)</span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> User <span class="title">get</span><span class="params">(Integer id)</span> </span>&#123;</span><br><span class="line">        logger.info(<span class="string">"进入get方法，当前获取对象：&#123;&#125;"</span>, userMap.get(id)==<span class="keyword">null</span>?<span class="keyword">null</span>:userMap.get(id).toString());</span><br><span class="line">        <span class="keyword">return</span> userMap.get(id);</span><br><span class="line">    &#125;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<p>为了方便演示数据库的操作，这里直接定义了一个Map&lt;Integer,User&gt; userMap，这里的核心是三个注解 <strong>@Cachable、@CachePut和@CacheEvict</strong></p>
<p>用缓存要注意，启动类要加上一个注解开启缓存</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br></pre></td><td class="code"><pre><span class="line"><span class="meta">@SpringBootApplication</span>(exclude=DataSourceAutoConfiguration.class)</span><br><span class="line"><span class="meta">@EnableCaching</span></span><br><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">Application</span> </span>&#123;</span><br><span class="line"></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="keyword">static</span> <span class="keyword">void</span> <span class="title">main</span><span class="params">(String[] args)</span> </span>&#123;</span><br><span class="line">        SpringApplication.run(Application.class, args);</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<h2 id="缓存注解"><a href="#缓存注解" class="headerlink" title="缓存注解"></a>缓存注解</h2><h3 id="Cacheable"><a href="#Cacheable" class="headerlink" title="@Cacheable"></a>@Cacheable</h3><p>根据方法的请求参数对其结果进行缓存</p>
<ul>
<li>key：缓存的key，可以为空，如果指定要按照SPEL表达式编写，如果不指定，则按照方法的所有参数进行组合。</li>
<li>value：缓存的名称，必须指定至少一个（如 @Cacheable (value=’user’)或者@Cacheable(value={‘user1’,’user2’})）</li>
<li>condition：缓存的条件，可以为空，使用SPEL编写，返回true或者false，只有为true才进行缓存。</li>
</ul>
<h3 id="CachePut"><a href="#CachePut" class="headerlink" title="@CachePut"></a>@CachePut</h3><p>根据方法的请求参数对其结果进行缓存，和@Cacheable不同的是，它每次都会触发真实方法的调用。参数描述见上。</p>
<h3 id="CacheEvict"><a href="#CacheEvict" class="headerlink" title="@CacheEvict"></a>@CacheEvict</h3><p>根据条件对缓存进行清空</p>
<ul>
<li>key：同上</li>
<li>value：同上</li>
<li>condition：同上</li>
<li>allEntries：是否清空所有缓存内容，缺省为false，如果指定为true，则方法调用后将立即清空所有缓存</li>
<li>beforeInvocation：是否在方法执行前就清空，缺省为false，如果指定为true，则在方法还没有执行的时候就清空缓存。缺省情况下，如果方法执行抛出异常，则不会清空缓存。</li>
</ul>
<hr>
<h1 id="缓存问题"><a href="#缓存问题" class="headerlink" title="缓存问题"></a>缓存问题</h1><h2 id="缓存和数据库数据一致性问题"><a href="#缓存和数据库数据一致性问题" class="headerlink" title="缓存和数据库数据一致性问题"></a>缓存和数据库数据一致性问题</h2><p>分布式环境下非常容易出现缓存和数据库间数据一致性问题，针对这一点，<strong>如果项目对缓存的要求是强一致性的，那么就不要使用缓存</strong>。我们只能采取合适的策略来降低缓存和数据库间数据不一致的概率，而无法保证两者间的强一致性。<strong>合适的策略包括合适的缓存更新策略，更新数据库后及时更新缓存、缓存失败时增加重试机制。</strong></p>
<h2 id="缓存雪崩？"><a href="#缓存雪崩？" class="headerlink" title="缓存雪崩？"></a>缓存雪崩？</h2><p>目前电商首页以及热点数据都会去做缓存，一般缓存都是定时任务去刷新，或者查不到之后去更新缓存的，定时任务刷新就有一个问题。</p>
<p>举个栗子：如果首页所有Key的失效时间都是12小时，中午12点刷新的，我零点有个大促活动大量用户涌入，假设每秒6000个请求，本来缓存可以抗住每秒5000个请求，但是<strong>缓存中所有Key都失效了。此时6000个/秒的请求全部落在了数据库上</strong>，数据库必然扛不住，真实情况可能DBA都没反应过来直接挂了，此时，如果没什么特别的方案来处理，DBA很着急，重启数据库，但是数据库立马又被新流量给打死了。这就是我理解的缓存雪崩。</p>
<p><strong>同一时间大面积失效，瞬间Redis跟没有一样，那这个数量级别的请求直接打到数据库几乎是灾难性的</strong>，你想想如果挂的是一个用户服务的库，那其他依赖他的库所有接口几乎都会报错，如果没做熔断等策略基本上就是瞬间挂一片的节奏，你怎么重启用户都会把你打挂，等你重启好的时候，用户早睡觉去了，临睡之前，骂骂咧咧“什么垃圾产品”。</p>
<h3 id="怎么应对？"><a href="#怎么应对？" class="headerlink" title="怎么应对？"></a>怎么应对？</h3><p>处理缓存雪崩简单，在批量往Redis存数据的时候，把每个Key的失效时间都加个随机值就好了，这样可以保证数据不会再同一时间大面积失效。</p>
<p>setRedis（key, value, time+Math.random()*10000）;<br>如果Redis是集群部署，将热点数据均匀分布在不同的Redis库中也能避免全部失效。或者设置热点数据永不过期，有更新操作就更新缓存就好了（比如运维更新了首页商品，那你刷下缓存就好了，不要设置过期时间），电商首页的数据也可以用这个操作，保险。</p>
<h2 id="缓存穿透和击穿？"><a href="#缓存穿透和击穿？" class="headerlink" title="缓存穿透和击穿？"></a>缓存穿透和击穿？</h2><p>先说下缓存穿透吧，<strong>缓存穿透是指缓存和数据库中都没有的数据，而用户（黑客）不断发起请求</strong>，举个栗子：我们数据库的id都是从1自增的，如果发起id=-1的数据或者id特别大不存在的数据，这样的不断攻击导致数据库压力很大，严重会击垮数据库。</p>
<p>至于缓存击穿嘛，这个跟缓存雪崩有点像，但是又有一点不一样，缓存雪崩是因为大面积的缓存失效，打崩了DB，而缓存击穿不同的是<strong>缓存击穿是指一个Key非常热点，在不停地扛着大量的请求</strong>，大并发集中对这一个点进行访问，<strong>当这个Key在失效的瞬间，持续的大并发直接落到了数据库上</strong>，就在这个Key的点上击穿了缓存。</p>
<h3 id="怎么解决？"><a href="#怎么解决？" class="headerlink" title="怎么解决？"></a>怎么解决？</h3><ol>
<li><p>缓存穿透我会在接口层增加校验，比如用户鉴权，参数做校验，不合法的校验直接return，比如id做基础校验，id&lt;=0直接拦截。</p>
</li>
<li><p>我记得Redis里还有一个高级用法布隆过滤器（Bloom Filter）这个也能很好的预防缓存穿透的发生，他的原理也很简单，就是利用高效的数据结构和算法快速判断出你这个Key是否在数据库中存在，不存在你return就好了，存在你就去查DB刷新KV再return。缓存击穿的话，设置热点数据永不过期，或者加上互斥锁就搞定了。作为暖男，代码给你准备好了，拿走不谢。</p>
</li>
</ol>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><span class="line"><span class="function"><span class="keyword">public</span> <span class="keyword">static</span> String <span class="title">getData</span><span class="params">(String key)</span> <span class="keyword">throws</span> InterruptedException </span>&#123;</span><br><span class="line">        <span class="comment">//从Redis查询数据</span></span><br><span class="line">        String result = getDataByKV(key);</span><br><span class="line">        <span class="comment">//参数校验</span></span><br><span class="line">        <span class="keyword">if</span> (StringUtils.isBlank(result)) &#123;</span><br><span class="line">            <span class="keyword">try</span> &#123;</span><br><span class="line">                <span class="comment">//获得锁</span></span><br><span class="line">                <span class="keyword">if</span> (reenLock.tryLock()) &#123;</span><br><span class="line">                    <span class="comment">//去数据库查询</span></span><br><span class="line">                    result = getDataByDB(key);</span><br><span class="line">                    <span class="comment">//校验</span></span><br><span class="line">                    <span class="keyword">if</span> (StringUtils.isNotBlank(result)) &#123;</span><br><span class="line">                        <span class="comment">//插进缓存</span></span><br><span class="line">                        setDataToKV(key, result);</span><br><span class="line">                    &#125;</span><br><span class="line">                &#125; <span class="keyword">else</span> &#123;</span><br><span class="line">                    <span class="comment">//睡一会再拿</span></span><br><span class="line">                    Thread.sleep(<span class="number">100L</span>);</span><br><span class="line">                    result = getData(key);</span><br><span class="line">                &#125;</span><br><span class="line">            &#125; <span class="keyword">finally</span> &#123;</span><br><span class="line">                <span class="comment">//释放锁</span></span><br><span class="line">                reenLock.unlock();</span><br><span class="line">            &#125;</span><br><span class="line">        &#125;</span><br><span class="line">        <span class="keyword">return</span> result;</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<h1 id="Redis为何这么快"><a href="#Redis为何这么快" class="headerlink" title="Redis为何这么快"></a>Redis为何这么快</h1><h2 id="redis作为缓存大家都在用，那redis一定很快咯？"><a href="#redis作为缓存大家都在用，那redis一定很快咯？" class="headerlink" title="redis作为缓存大家都在用，那redis一定很快咯？"></a>redis作为缓存大家都在用，那redis一定很快咯？</h2><p>当然了，官方提供的数据可以达到100000+的QPS（每秒内的查询次数），这个数据不比Memcached差！</p>
<h2 id="redis这么快，它的“多线程模型”你了解吗？（露出邪魅一笑）"><a href="#redis这么快，它的“多线程模型”你了解吗？（露出邪魅一笑）" class="headerlink" title="redis这么快，它的“多线程模型”你了解吗？（露出邪魅一笑）"></a>redis这么快，它的“多线程模型”你了解吗？（露出邪魅一笑）</h2><p>这是想问Redis这么快，为什么还是单线程的吧。Redis确实是单进程单线程的模型，因为Redis完全是基于内存的操作，CPU不是Redis的瓶颈，Redis的瓶颈最有可能是机器内存的大小或者网络带宽。既然单线程容易实现，而且CPU不会成为瓶颈，那就顺理成章的采用单线程的方案了（毕竟采用多线程会有很多麻烦）。</p>
<h2 id="Redis是单线程的，为什么还能这么快吗？"><a href="#Redis是单线程的，为什么还能这么快吗？" class="headerlink" title="Redis是单线程的，为什么还能这么快吗？"></a>Redis是单线程的，为什么还能这么快吗？</h2><ol>
<li>Redis完全基于内存，绝大部分请求是纯粹的内存操作，非常迅速，数据存在内存中，类似于HashMap，HashMap的优势就是查找和操作的时间复杂度是O(1)。</li>
<li>数据结构简单，对数据操作也简单。</li>
<li>采用单线程，避免了不必要的上下文切换和竞争条件，不存在多线程导致的CPU切换，不用去考虑各种锁的问题，不存在加锁释放锁操作，没有死锁问题导致的性能消耗。</li>
<li>使用多路复用IO模型，非阻塞IO。</li>
</ol>
<h2 id="Redis和Memcached的区别"><a href="#Redis和Memcached的区别" class="headerlink" title="Redis和Memcached的区别"></a>Redis和Memcached的区别</h2><ol>
<li>存储方式上：memcache会把数据全部存在内存之中，断电后会挂掉，数据不能超过内存大小。redis有部分数据存在硬盘上，这样能保证数据的持久性。</li>
<li>数据支持类型上：memcache对数据类型的支持简单，只支持简单的key-value，，而redis支持五种数据类型。</li>
<li>性能对比，redis只使用单核而memcached使用多核，所以平均每一个核上Rdis在存储小数据时比Memcached性能更高。100k以上的数据中，memcached性能更高。</li>
<li>集群模式:memcached没有原生的集群模式，需要依靠客户端来实现向集群中分片写入数据；但是redis目前是原生支持cluster模式的，redis官方就是支持redis cluster集群模式的，比memcached来说要更好。</li>
<li>value的大小：redis可以达到1GB，而memcache只有1MB</li>
</ol>
<hr>
<h1 id="Redis单线程模型"><a href="#Redis单线程模型" class="headerlink" title="Redis单线程模型"></a>Redis单线程模型</h1><h2 id="文件事件处理器"><a href="#文件事件处理器" class="headerlink" title="文件事件处理器"></a>文件事件处理器</h2><p>redis基于reactor模式开发了网络事件处理器，这个处理器叫做文件事件处理器，file event handler。这个文件事件处理器，是单线程的，redis才叫做单线程的模型，采用IO多路复用机制同时监听多个socket，根据socket上的事件来选择对应的事件处理器来处理这个事件。</p>
<blockquote>
<p>Reactor模式首先是事件驱动的，有一个或多个并发输入源，有一个Service Handler，有多个Request Handlers；这个Service Handler会同步的将输入的请求（Event）多路复用的分发给相应的Request Handler。<br><img src="https://s2.ax1x.com/2019/12/28/lezWdS.png" alt="lezWdS.png"></p>
</blockquote>
<p>如果被监听的socket准备好执行accept、read、write、close等操作的时候，跟操作对应的文件事件就会产生，这个时候文件事件处理器就会调用之前关联好的事件处理器来处理这个事件。</p>
<p>文件事件处理器是单线程模式运行的，但是通过IO多路复用机制监听多个socket，可以实现高性能的网络通信模型，又可以跟内部其他单线程的模块进行对接，保证了redis内部的线程模型的简单性。</p>
<p>文件事件处理器的结构包含4个部分：<strong>多个socket，IO多路复用程序，文件事件分派器，事件处理器（命令请求处理器、命令回复处理器、连接应答处理器，等等）。</strong></p>
<p>多个socket可能并发的产生不同的操作，每个操作对应不同的文件事件，但是IO多路复用程序会监听多个socket，但是会将socket放入一个队列中排队，每次从队列中取出一个socket给事件分派器，事件分派器把socket给对应的事件处理器。</p>
<p>然后一个socket的事件处理完之后，IO多路复用程序才会将队列中的下一个socket给事件分派器。文件事件分派器会根据每个socket当前产生的事件，来选择对应的事件处理器来处理。</p>
<h2 id="文件事件"><a href="#文件事件" class="headerlink" title="文件事件"></a>文件事件</h2><p>当socket变得可读时（比如客户端对redis执行write操作，或者close操作），或者有新的可以应答的socket出现时（客户端对redis执行connect操作），socket就会产生一个<strong>AE_READABLE</strong>事件。</p>
<p>当socket变得可写的时候（客户端对redis执行read操作），socket会产生一个<strong>AE_WRITABLE</strong>事件。</p>
<p>IO多路复用程序可以同时监听 <strong>AE_REABLE和AE_WRITABLE</strong> 两种事件，要是一个socket同时产生<strong>AE_REABLE和AE_WRITABLE</strong> 两种事件，那么文件事件分派器<strong>优先处理AE_REABLE事件</strong>，然后才是AE_WRITABLE事件。</p>
<h2 id="文件事件处理器-1"><a href="#文件事件处理器-1" class="headerlink" title="文件事件处理器"></a>文件事件处理器</h2><ul>
<li>如果是客户端要连接redis，那么会为socket关联<strong>连接应答处理器</strong> </li>
<li>如果是客户端要写数据到redis，那么会为socket关联<strong>命令请求处理器</strong> </li>
<li>如果是客户端要从redis读数据，那么会为socket关<strong>联命令回复处理器</strong>  </li>
</ul>
<h2 id="客户端与redis通信的一次流程"><a href="#客户端与redis通信的一次流程" class="headerlink" title="客户端与redis通信的一次流程"></a>客户端与redis通信的一次流程</h2><p><img src="https://s2.ax1x.com/2019/12/28/lmS6fJ.png" alt="lmS6fJ.png"></p>
<p>在redis启动初始化的时候，redis会将连接应答处理器跟AE_READABLE事件关联起来，接着如果一个客户端跟redis发起连接，此时会产生一个AE_READABLE事件，然后由连接应答处理器来处理跟客户端建立连接，创建客户端对应的socket，同时将这个socket的AE_READABLE事件跟命令请求处理器关联起来。</p>
<p>当客户端向redis发起请求的时候（不管是读请求还是写请求，都一样），首先就会在socket产生一个AE_READABLE事件，然后由对应的命令请求处理器来处理。这个命令请求处理器就会从socket中读取请求相关数据，然后进行执行和处理。</p>
<p>接着redis这边准备好了给客户端的响应数据之后，就会将socket的AE_WRITABLE事件跟命令回复处理器关联起来，当客户端这边准备好读取响应数据时，就会在socket上产生一个AE_WRITABLE事件，会由对应的命令回复处理器来处理，就是将准备好的响应数据写入socket，供客户端来读取。</p>
<p>命令回复处理器写完之后，就会删除这个socket的AE_WRITABLE事件和命令回复处理器的关联关系。</p>
<hr>
<h1 id="过期策略"><a href="#过期策略" class="headerlink" title="过期策略"></a>过期策略</h1><p><strong>定期删除+惰性删除</strong> </p>
<p><strong>定期删除</strong>，指的是redis默认是每隔100ms就随机抽取一些设置了过期时间的key，检查其是否过期，如果过期就删除。</p>
<p>假设redis里放了10万个key，都设置了过期时间，你每隔几百毫秒，就检查10万个key，那redis基本上就死了，cpu负载会很高的，消耗在你的检查过期key上了。注意，这里可不是每隔100ms就遍历所有的设置过期时间的key，那样就是一场性能上的灾难。实际上redis是每隔100ms随机抽取一些key来检查和删除的。</p>
<p>但是问题是，定期删除可能会导致很多过期key到了时间并没有被删除掉，那咋整呢？所以就是<strong>惰性删除了</strong>。这就是说，在你获取某个key的时候，redis会检查一下 ，这个key如果设置了过期时间那么是否过期了？如果过期了此时就会删除，不会给你返回任何东西。</p>
<p>并不是key到时间就被删除掉，而是你查询这个key的时候，redis再懒惰的检查一下</p>
<p>通过上述两种手段结合起来，保证过期的key一定会被干掉。</p>
<hr>
<h1 id="淘汰策略"><a href="#淘汰策略" class="headerlink" title="淘汰策略"></a>淘汰策略</h1><p>实际上，如果定期删除漏掉了很多过期key，然后你也没及时去查，也就没走惰性删除，此时会怎么样？如果大量过期key堆积在内存里，导致redis内存块耗尽了，咋整？</p>
<p>答案是：走内存淘汰机制</p>
<p><img src="https://s2.ax1x.com/2019/12/27/lVCzqK.jpg" alt="lVCzqK.jpg"></p>
<p><strong>补充一下：</strong><br>Redis4.0加入了LFU(least frequency use)淘汰策略，包括volatile-lfu和allkeys-lfu，通过统计访问频率，将访问频率最少，即最不经常使用的KV淘汰。</p>
<ul>
<li>volatile-lru：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，移除最近最少使用的key（这个一般不太合适）</li>
<li>volatile-ttl：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，有更早过期时间的key优先移除</li>
<li>volatile-random：当内存不足以容纳新写入数据时，在设置了过期时间的键空间中，随机移除某个key</li>
<li>allkeys-lru：当内存不足以容纳新写入数据时，在键空间中，移除最近最少使用的key（这个是最常用的）</li>
<li>allkeys-random：当内存不足以容纳新写入数据时，在键空间中，随机移除某个key，这个一般没人用吧，为啥要随机，肯定是把最近最少使用的key给干掉啊</li>
<li>noeviction：当内存不足以容纳新写入数据时，新写入操作会报错，这个一般没人用吧，实在是太恶心了</li>
</ul>
<h2 id="手写lru算法"><a href="#手写lru算法" class="headerlink" title="手写lru算法"></a>手写lru算法</h2><figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br></pre></td><td class="code"><pre><span class="line"><span class="keyword">public</span> <span class="class"><span class="keyword">class</span> <span class="title">LRUCache</span>&lt;<span class="title">K</span>, <span class="title">V</span>&gt; <span class="keyword">extends</span> <span class="title">LinkedHashMap</span>&lt;<span class="title">K</span>, <span class="title">V</span>&gt; </span>&#123;</span><br><span class="line">    </span><br><span class="line">    <span class="keyword">private</span> <span class="keyword">final</span> <span class="keyword">int</span> CACHE_SIZE;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// 这里就是传递进来最多能缓存多少数据</span></span><br><span class="line">    <span class="function"><span class="keyword">public</span> <span class="title">LRUCache</span><span class="params">(<span class="keyword">int</span> cacheSize)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 这块就是设置一个hashmap的初始大小</span></span><br><span class="line">        <span class="comment">// 同时最后一个true指的是让linkedhashmap按照访问顺序来进行排序</span></span><br><span class="line">        <span class="comment">// 最近访问的放在头，最老访问的就在尾</span></span><br><span class="line">        <span class="keyword">super</span>((<span class="keyword">int</span>) Math.ceil(cacheSize / <span class="number">0.75</span>) + <span class="number">1</span>, <span class="number">0.75f</span>, <span class="keyword">true</span>); </span><br><span class="line">        CACHE_SIZE = cacheSize;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">    <span class="meta">@Override</span></span><br><span class="line">    <span class="function"><span class="keyword">protected</span> <span class="keyword">boolean</span> <span class="title">removeEldestEntry</span><span class="params">(Map.Entry eldest)</span> </span>&#123;</span><br><span class="line">        <span class="comment">// 这个意思就是说当map中的数据量大于指定的缓存个数的时候，就自动删除最老的数据</span></span><br><span class="line">        <span class="keyword">return</span> size() &gt; CACHE_SIZE;</span><br><span class="line">    &#125;</span><br><span class="line"></span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>

<hr>
<h1 id="持久化"><a href="#持久化" class="headerlink" title="持久化"></a>持久化</h1><h2 id="redis的持久化机制？"><a href="#redis的持久化机制？" class="headerlink" title="redis的持久化机制？"></a>redis的持久化机制？</h2><p>redis为了保证效率，数据缓存在了内存中，但是会周期性的把更新的数据写入磁盘或者把修改操作写入追加的记录文件中，以保证数据的持久化。Redis的持久化策略有两种：</p>
<ul>
<li><p>RDB：快照形式是直接把内存中的数据保存到一个dump的文件中，定时保存，保存策略。</p>
</li>
<li><p>AOF：把所有的对Redis的服务器进行修改的命令都存到一个文件里，命令的集合。Redis默认是快照RDB的持久化方式。</p>
</li>
</ul>
<p>当Redis重启的时候，它会优先使用AOF文件来还原数据集，因为AOF文件保存的数据集通常比RDB文件所保存的数据集更完整。你甚至可以关闭持久化功能，让数据只在服务器运行时存。</p>
<h2 id="RDB是怎么工作的？"><a href="#RDB是怎么工作的？" class="headerlink" title="RDB是怎么工作的？"></a>RDB是怎么工作的？</h2><p>默认Redis是会以快照”RDB”的形式将数据持久化到磁盘的一个二进制文件dump.rdb。</p>
<h3 id="工作原理"><a href="#工作原理" class="headerlink" title="工作原理"></a>工作原理</h3><p>当Redis需要做持久化时，Redis会fork一个子进程，子进程将数据写到磁盘上一个临时RDB文件中。当子进程完成写临时文件后，将原来的RDB替换掉，这样的好处是可以copy-on-write。</p>
<h3 id="RDB的优"><a href="#RDB的优" class="headerlink" title="RDB的优"></a>RDB的优</h3><p>这种文件非常适合用于备份：比如，你可以在最近的24小时内，每小时备份一次，并且在每个月的每一天也备份一个RDB文件。这样的话，即使遇上问题，也可以随时将数据集还原到不同的版本。RDB非常适合灾难恢复。RDB的缺点是：如果你需要尽量避免在服务器故障时丢失数据，那么RDB不合适你。</p>
<h2 id="AOF是怎么工作的？"><a href="#AOF是怎么工作的？" class="headerlink" title="AOF是怎么工作的？"></a>AOF是怎么工作的？</h2><p>使用AOF做持久化，每一个写命令都通过write函数追加到appendonly.aof中，配置方式如下：</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><span class="line">appendfsync yes   </span><br><span class="line">appendfsync always    </span><br><span class="line">#每次有数据修改发生时都会写入AOF文件。</span><br><span class="line">#每秒钟同步一次，该策略为AOF的缺省策略。</span><br><span class="line">appendfsync everysec</span><br></pre></td></tr></table></figure>

<h3 id="工作原理-1"><a href="#工作原理-1" class="headerlink" title="工作原理"></a>工作原理</h3><p>AOF可以做到全程持久化，只需要在配置中开启 appendonly yes。这样redis每执行一个修改数据的命令，都会把它添加到AOF文件中，当redis重启时，将会读取AOF文件进行重放，恢复到redis关闭前的最后时刻。</p>
<h3 id="使用AOF的优点"><a href="#使用AOF的优点" class="headerlink" title="使用AOF的优点"></a>使用AOF的优点</h3><p>让redis变得非常耐久。可以设置不同的fsync策略，aof的默认策略是每秒钟fsync一次，在这种配置下，就算发生故障停机，也最多丢失一秒钟的数据。缺点是对于相同的数据集来说，AOF的文件体积通常要大于RDB文件的体积。根据所使用的fsync策略，AOF的速度可能会慢于RDB。</p>
<h2 id="该用哪一个呢？"><a href="#该用哪一个呢？" class="headerlink" title="该用哪一个呢？"></a>该用哪一个呢？</h2><p>如果你非常关心你的数据，但仍然可以承受数分钟内的数据丢失，那么可以额只使用RDB持久。</p>
<p>AOF将Redis执行的每一条命令追加到磁盘中，处理巨大的写入会降低Redis的性能，不知道你是否可以接受。</p>
<p>数据库备份和灾难恢复：定时生成RDB快照非常便于进行数据库备份，并且RDB恢复数据集的速度也要比AOF恢复的速度快。当然了，redis支持同时开启RDB和AOF，系统重启后，redis会优先使用AOF来恢复数据，这样丢失的数据会最少。</p>
<hr>
<h1 id="主从复制"><a href="#主从复制" class="headerlink" title="主从复制"></a>主从复制</h1><p>redis单节点存在单点故障问题，为了解决单点问题，一般都需要对redis配置从节点，然后使用哨兵来监听主节点的存活状态，如果主节点挂掉，从节点能继续提供缓存功能。</p>
<p>主从配置结合哨兵模式能解决单点故障问题，提高redis可用性。从节点仅提供读操作，主节点提供写操作。对于读多写少的状况，可给主节点配置多个从节点，从而提高响应效率。</p>
<h2 id="redis-replication核心机制"><a href="#redis-replication核心机制" class="headerlink" title="redis replication核心机制"></a>redis replication核心机制</h2><ol>
<li>redis <strong>采用异步方式</strong> 复制数据到slave节点，不过redis 2.8开始，slave node会周期性的确认自己每次复制的数据量。</li>
<li>一个master node是<strong>可以配置多个slave node</strong>的。</li>
<li>slave node也可以连接其他的slave node。</li>
<li>slave node做复制的时候，是不会block master node的正常工作的。</li>
<li>slave node在做复制的时候，也不会对自己的查询操作有影响，它会用旧的数据集来提供服务；但是，<strong>复制完成的时候，需要删除旧的数据集，加载新的数据集</strong>，这个时候就会暂停对外服务了。</li>
<li>slave node主要<strong>用来横向扩容，做读写分离，扩容的slave node可以提高读的吞吐量</strong>。</li>
</ol>
<h2 id="master持久化对主从架构的意义"><a href="#master持久化对主从架构的意义" class="headerlink" title="master持久化对主从架构的意义"></a>master持久化对主从架构的意义</h2><p>如果采用了主从架构，那么<strong>建议必须开启master node持久化!!</strong></p>
<p>不建议使用slave node作为master node的数据热备，因为那样的话，如果关掉master的持久化，可能在master宕机重启的时候数据是空的，然后经过复制，slave节点也丢失了。</p>
<blockquote>
<p>master：RDB和AOF都关闭了，数据都存在内存中  </p>
<p>master宕机，重启，是没有本地数据可以恢复的，然后就会认为自己的IDE数据是空的，然后将空的数据集同步到slave上去，所有slave的数据集全部清空。</p>
<p>造成100%数据丢失。</p>
<p>即使采用了高可用机制，slave node自动接管master node，但是也可能sentinal还没有检测到master failure，master node自动重启了，还是可能会造成slave node数据清空。</p>
</blockquote>
<h2 id="复制的过程"><a href="#复制的过程" class="headerlink" title="复制的过程"></a>复制的过程</h2><p>关于复制过程，是这样的：</p>
<ol>
<li>从节点启动，执行slaveof [masterIP] [masterPort] <em>(redis.cnf里边的slaveof配置的)</em>， 保存主节点信息</li>
<li>从节点中的定时任务 <em>(每秒检查是否有新的master node要来连接和复制)</em> 发现主节点信息，建立和主节点的socket连接</li>
<li>从节点发送Ping信号，主节点返回Pong，两边能互相通信</li>
<li>口令认证，如果master设置了requirepass，那么slave node必须发送masterauth的口令过去认证</li>
<li>连接建立后，主节点第一次会进行全量复制(将所有数据发送给从节点)</li>
<li>主节点把当前的数据同步给从节点后，便完成了复制的建立过程。接下来，主节点就会持续的把写命令发送给从节点，保证主从数据一致性。</li>
</ol>
<h2 id="数据同步的过程"><a href="#数据同步的过程" class="headerlink" title="数据同步的过程"></a>数据同步的过程</h2><p>redis2.8之前使用sync [runId] [offset] 同步命令，redis2.8之后使用psync [runId] [offset] 命令。</p>
<p>两者不同在于，sync命令仅支持全量复制过程，psync支持全量和部分复制。介绍同步之前，先介绍几个概念：</p>
<ul>
<li><p>runId：每个redis节点启动都会生成唯一的uuid，每次redis重启后，runId都会发生变化。info server 可以看到master run id。如果只根据host+ip定位master node是不靠谱的。若发现slave node发送的同步请求中runId 不同，则触发全量复制，同步给slave node。如果需要不更改run id重启redis，可以使用redis-cli debug reload命令。</p>
</li>
<li><p>offset：主节点和从节点都各自维护自己的主从复制偏移量offset，当主节点有写入命令时，offset=offset+命令的字节长度。从节点在收到主节点发送的命令后，也会增加自己的offset，并把自己的offset发送给主节点。这样，<strong>主节点同时保存自己的offset和从节点的offset</strong>，通过对比offset来判断主从节点数据是否一致。</p>
</li>
<li><p>repl_backlog_size：保存在主节点上的一个固定长度的先进先出队列，默认大小是1MB。master node给slave node复制数据时，也会将数据在backlog中同步写一份。</p>
</li>
</ul>
<h3 id="psync的执行流程："><a href="#psync的执行流程：" class="headerlink" title="psync的执行流程："></a>psync的执行流程：</h3><p><img src="https://s2.ax1x.com/2019/12/28/leIvjJ.png" alt="leIvjJ.png"></p>
<p>从节点发送psync[runId][offset]命令，主节点有三种响应：</p>
<ul>
<li>FULLRESYNC：第一次连接，进行全量复制</li>
<li>CONTINUE：之后的连接，进行部分复制</li>
<li>ERR：不支持psync命令，进行全量复制</li>
</ul>
<blockquote>
<p>从节点第一次连接到主节点时，主节点会发送<strong>FULLRESYNC</strong> 进行全量复制，<strong>之后的每次复制都进行部分复制。</strong></p>
</blockquote>
<blockquote>
<p>主节点响应写命令时，<strong>不但会把命名发送给从节点，还会写入复制积压缓冲区</strong>，用于复制命令丢失的数据补救。</p>
</blockquote>
<h2 id="全量复制和部分复制"><a href="#全量复制和部分复制" class="headerlink" title="全量复制和部分复制"></a>全量复制和部分复制</h2><p><img src="https://s2.ax1x.com/2019/12/28/leoi4K.png" alt="leoi4K.png"></p>
<h3 id="全量复制的流程"><a href="#全量复制的流程" class="headerlink" title="全量复制的流程"></a>全量复制的流程</h3><ul>
<li><p>从节点发送psync ? -1命令（因为第一次发送，不知道主节点的runId，所以为?，因为是第一次复制，所以offset=-1）。</p>
</li>
<li><p>主节点发现从节点是第一次复制，返回FULLRESYNC {runId} {offset}，runId是主节点的runId，offset是主节点目前的offset。</p>
</li>
<li><p>从节点接收主节点信息后，保存到info中。</p>
</li>
<li><p>主节点在发送FULLRESYNC后，启动bgsave命令，生成RDB文件（数据持久化）。</p>
</li>
<li><p>主节点发送RDB文件给从节点，如果rdb复制事件超过60s(repl-timeout)，那么slave node就会认为复制失败，可以适当调大这个参数。</p>
</li>
<li><p>主节点在生成rdb时，会将所有新的写命令缓存在内存中，在从节点保存了rdb之后，再将新的写命令复制给从节点。</p>
</li>
<li><p><em>client-output-buffer-limit slave 256MB 64MB 60</em>，如果在复制期间，内存缓冲区持续消耗超过64MB，或者一次性超过256MB，那么停止复制，复制失败。</p>
</li>
<li><p>从节点收到rdb之后，清空自己的旧数据，然后重新加载RDB文件到内存中，同时基于旧的数据版本对外提供服务。</p>
</li>
<li><p>如果从节点开启了AOF，从节点会异步重写AOF文件。</p>
</li>
</ul>
<blockquote>
<p>如果复制的数据量在4G~6G之间，那么很可能全量复制时间消耗在1分半到2分钟。</p>
</blockquote>
<h3 id="部分复制-增量复制"><a href="#部分复制-增量复制" class="headerlink" title="部分复制(增量复制)"></a>部分复制(增量复制)</h3><ul>
<li><p>部分复制主要是Redis针对全量复制的过高开销做出的一种优化措施，使用psync [runId] [offset] 命令实现。当从节点正在复制主节点时，如果<strong>出现网络闪断或者命令丢失等</strong>异常情况时，<strong>从节点会向主节点要求补发丢失的命令数据</strong>，主节点的复制积压缓冲区（backlog）将这部分数据直接发送给从节点，这样就可以保持主从节点复制的一致性。补发的这部分数据一般远远小于全量数据。</p>
</li>
<li><p>主从连接中断期间主节点依然响应命令，但因复制连接中断命令无法发送给从节点，不过主节点内的复制积压缓冲区（backlog）依然可以保存最近一段时间的写命令数据。</p>
</li>
<li><p>当主从连接恢复后，由于从节点之前保存了自身已复制的偏移量和主节点的运行ID。因此会把它们当做psync参数发送给主节点，要求进行部分复制。</p>
</li>
<li><p>主节点接收到psync命令后首先核对参数runId是否与自身一致，如果一致，说明之前复制的是当前主节点；之后根据参数offset在复制积压缓冲区（backlog）中查找，如果offset之后的数据存在，则对从节点发送+CONTINUE命令，表示可以进行部分复制。因为缓冲区大小固定，若发生缓冲溢出，则进行全量复制。</p>
</li>
<li><p>主节点根据偏移量把复制积压缓冲区（backlog）里的数据发送给从节点，保证主从复制进入正常状态。</p>
</li>
</ul>
<h2 id="心跳机制"><a href="#心跳机制" class="headerlink" title="心跳机制"></a>心跳机制</h2><p>主从节点互相都会发送heartbeat信息  </p>
<p>master默认每隔10秒发送一次heartbeat，slave node每隔1秒发送一次heartbeat。</p>
<h2 id="主从复制断点续传"><a href="#主从复制断点续传" class="headerlink" title="主从复制断点续传"></a>主从复制断点续传</h2><p>从redis2.8开始，支持主从复制断点续传，如果主从复制过程中，网络连接断了，那么可以接着上次复制的地方，继续复制下去，而不是重新开始。</p>
<p>master node会在内存中创建一个backlog，master和slave都会保存一个replicate offset还有一个master id，offset就是保存在backlog中的。如果master和slave网络链接断掉了，slave会让master 从上次的replicat offset开始继续复制，如果没有找到对应的offset，那么就会执行一次resynchronization。</p>
<h2 id="无磁盘化复制"><a href="#无磁盘化复制" class="headerlink" title="无磁盘化复制"></a>无磁盘化复制</h2><p>master在内存中直接创建rdb，然后发送给slave，不会再自己本地落地磁盘</p>
<figure class="highlight plain"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><span class="line">repl-diskless-sync</span><br><span class="line"># 等待一定时间在开始复制，因为要等更多的slave重新连接过来</span><br><span class="line">repl-diskless-sync-delay</span><br></pre></td></tr></table></figure>

<h2 id="过期Key"><a href="#过期Key" class="headerlink" title="过期Key"></a>过期Key</h2><p>slave 不存在过期key，智慧等待master过期key。如果master过期或者LRU淘汰了一个key，那么会模拟一个del命令发送给slave。</p>
<h2 id="主从复制会存在的问题"><a href="#主从复制会存在的问题" class="headerlink" title="主从复制会存在的问题"></a>主从复制会存在的问题</h2><ul>
<li><p>一旦主节点宕机，从节点晋升为主节点，同时需要修改应用方的主节点地址，还需要命令所有从节点去复制新的主节点，整个过程需要人工干预。</p>
</li>
<li><p>主节点的写能力受到单机的限制。</p>
</li>
<li><p>主节点的存储能力受到单机的限制。</p>
</li>
<li><p>原生复制的弊端在早期的版本中也会比较突出，比如：redis复制中断后，从节点会发起psync。此时如果同步不成功，则会进行全量同步，主库执行全量备份的同时，可能会造成毫秒或秒级的卡顿。</p>
</li>
</ul>
<h3 id="比较主流的解决方案？"><a href="#比较主流的解决方案？" class="headerlink" title="比较主流的解决方案？"></a>比较主流的解决方案？</h3><p>哨兵。</p>
<hr>
<h1 id="哨兵"><a href="#哨兵" class="headerlink" title="哨兵"></a>哨兵</h1><p><img src="https://s2.ax1x.com/2019/12/28/leoXIP.png" alt="leoXIP.png"></p>
<p>如图，是Redis Sentinel（哨兵）的架构图。Redis Sentinel（哨兵）主要功能包括主节点存活检测、主从运行情况检测、自动故障转移、主从切换。Redis Sentinel最小配置是一主一从。</p>
<p>Redis的Sentinel系统可以用来管理多个Redis服务器，该系统可以执行以下四个任务：</p>
<ul>
<li><p>监控：不断检查主服务器和从服务器是否正常运行。</p>
</li>
<li><p>通知：当被监控的某个redis服务器出现问题，Sentinel通过API脚本向管理员或者其他应用程序发出通知。</p>
</li>
<li><p>自动故障转移：当主节点不能正常工作时，Sentinel会开始一次自动的故障转移操作，它会将与失效主节点是主从关系的其中一个从节点升级为新的主节点，并且将其他的从节点指向新的主节点，这样人工干预就可以免了。</p>
</li>
<li><p>配置提供者：在Redis Sentinel模式下，客户端应用在初始化时连接的是Sentinel节点集合，从中获取主节点的信息。</p>
</li>
</ul>
<h2 id="哨兵的工作原理"><a href="#哨兵的工作原理" class="headerlink" title="哨兵的工作原理"></a>哨兵的工作原理</h2><ol>
<li>每个Sentinel节点都需要定期执行以下任务：每个Sentinel以每秒一次的频率，向它所知的主服务器、从服务器以及其他的Sentinel实例发送一个PING命令。（如图）</li>
</ol>
<p><img src="https://s2.ax1x.com/2019/12/28/leTyJf.png" alt="leTyJf.png"></p>
<ol start="2">
<li><p>如果一个实例距离最后一次有效回复PING命令的时间超过down-after-milliseconds所指定的值，那么这个实例会被Sentinel标记为主观下线。（如图）<br><img src="https://s2.ax1x.com/2019/12/28/leTBdI.png" alt="leTBdI.png"></p>
</li>
<li><p>如果一个主服务器被标记为主观下线，那么正在监视这个服务器的所有Sentinel节点，要以每秒一次的频率确认主服务器的确进入了主观下线状态。<br><img src="https://s2.ax1x.com/2019/12/28/leTsFP.png" alt="leTsFP.png"></p>
</li>
<li><p>如果一个主服务器被标记为主观下线，并且有足够数量的Sentinel（至少要达到配置文件指定的数量）在指定的时间范围内同意这一判断，那么这个主服务器被标记为客观下线。<br><img src="https://s2.ax1x.com/2019/12/28/le7Emd.png" alt="le7Emd.png"></p>
</li>
<li><p>一般情况下，每个Sentinel会以每10秒一次的频率向它已知的所有主服务器和从服务器发送INFO命令，当一个主服务器被标记为客观下线时，Sentinel向下线主服务器的所有从服务器发送INFO命令的频率，会从10秒一次改为每秒一次。<br><img src="https://s2.ax1x.com/2019/12/28/le7kOH.png" alt="le7kOH.png"></p>
</li>
<li><p>Sentinel和其他Sentinel协商客观下线的主节点的状态，如果处于SDOWN状态，则投票自动选出新的主节点，将剩余从节点指向新的主节点进行数据复制。<br><img src="https://s2.ax1x.com/2019/12/28/le7PSO.png" alt="le7PSO.png"></p>
</li>
<li><p>当没有足够数量的Sentinel同意主服务器下线时，主服务器的客观下线状态就会被移除。当主服务器重新向Sentinel的PING命令返回有效回复时，主服务器的主观下线状态就会被移除<br><img src="https://s2.ax1x.com/2019/12/28/le79fK.png" alt="le79fK.png"></p>
</li>
</ol>
<hr>
<h1 id="生产环境中redis是怎么部署的"><a href="#生产环境中redis是怎么部署的" class="headerlink" title="生产环境中redis是怎么部署的"></a>生产环境中redis是怎么部署的</h1><h2 id="机器什么配置"><a href="#机器什么配置" class="headerlink" title="机器什么配置"></a>机器什么配置</h2><p>32G内存 + 8核CPU + 1T磁盘。分配给redis是10g内存。(生产环境，redis内存尽量不超过10g)</p>
<h2 id="redis-cluster"><a href="#redis-cluster" class="headerlink" title="redis cluster"></a>redis cluster</h2><p>10台机器，5台机器部署了redis主实例，另外5台机器部署了redis的从实例，每个主实例挂了一个从实例，5个节点对外提供写服务，每个节点的读写高峰QPS可能达到每秒5万，5台机器最多是每秒25万读写请求。</p>
<p>高峰请求 3500请求/s.</p>
<p>因为每个主实例都挂了一个从实例，所以是高可用的，任何一个主实例宕机，都会自动故障迁移，redis从实例会自动变成主实例继续提供读写服务。</p>
<h2 id="内存中写的是什么数据？每条数据大小是多少？"><a href="#内存中写的是什么数据？每条数据大小是多少？" class="headerlink" title="内存中写的是什么数据？每条数据大小是多少？"></a>内存中写的是什么数据？每条数据大小是多少？</h2><p>商品数据，每条数据10kb.100条是1mb，10万条数据是1g。常驻内存是200万条数据，占用内存是20g，仅仅不到中内存的50%。</p>

      
    </div>
    
    
    
    <div>
      
        <div>
    
        <div style="text-align:center;color: #ccc;font-size:14px;">-------------本文结束 <i class="fa fa-paw"></i> 感谢您的阅读-------------</div>
    
</div>

      
    </div>
    <div>
      
        
<div class="my_post_copyright">
  <script src="//cdn.bootcss.com/clipboard.js/1.5.10/clipboard.min.js"></script>
  
  <!-- JS库 sweetalert 可修改路径 -->
  <script src="https://cdn.bootcss.com/jquery/2.0.0/jquery.min.js"></script>
  <script src="https://unpkg.com/sweetalert/dist/sweetalert.min.js"></script>
  <p><span>本文标题:</span><a href="/redis-learn/">Redis基础、原理全覆盖</a></p>
  <p><span>文章作者:</span><a href="/" title="访问 Big Jelly 的个人博客">Big Jelly</a></p>
  <p><span>发布时间:</span>2019年12月22日 - 17:32</p>
  <p><span>最后更新:</span>2020年04月04日 - 20:25</p>
  <p><span>原始链接:</span><a href="/redis-learn/" title="Redis基础、原理全覆盖">https://jelly54.github.io/redis-learn/</a>
    <span class="copy-path" title="点击复制文章链接"><i class="fa fa-clipboard" data-clipboard-text="https://jelly54.github.io/redis-learn/" aria-label="复制成功！"></i></span>
  </p>
  <p><span>许可协议:</span><i class="fa fa-creative-commons"></i> <a rel="license" href="https://creativecommons.org/licenses/by-nc-nd/4.0/" target="_blank" title="Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0)">署名-非商业性使用-禁止演绎 4.0 国际</a> 转载请保留原文链接及作者。</p>  
</div>
<script> 
    var clipboard = new Clipboard('.fa-clipboard');
      $(".fa-clipboard").click(function(){
      clipboard.on('success', function(){
        swal({   
          title: "",   
          text: '复制成功',
          icon: "success", 
          showConfirmButton: true
          });
        });
    });  
</script>

      
    </div>
    

    
      <div>
        <div style="padding: 10px 0; margin: 20px auto; width: 90%; text-align: center;">
  <div>坚持原创技术分享，您的支持将鼓励我继续创作！</div>
  <button id="rewardButton" disable="enable" onclick="var qr = document.getElementById('QR'); if (qr.style.display === 'none') {qr.style.display='block';} else {qr.style.display='none'}">
    <span>打赏</span>
  </button>
  <div id="QR" style="display: none;">

    
      <div id="wechat" style="display: inline-block">
        <img id="wechat_qr" src="/images/wechatpay.png" alt="Big Jelly 微信支付">
        <p>微信支付</p>
      </div>
    

    
      <div id="alipay" style="display: inline-block">
        <img id="alipay_qr" src="/images/alipay.png" alt="Big Jelly 支付宝">
        <p>支付宝</p>
      </div>
    

    

  </div>
</div>

      </div>
    

    

    <footer class="post-footer">
      
        <div class="post-tags">
          
            <a href="/tags/缓存穿透/" rel="tag"><i class="fa fa-tag"></i> 缓存穿透</a>
          
            <a href="/tags/缓存雪崩/" rel="tag"><i class="fa fa-tag"></i> 缓存雪崩</a>
          
            <a href="/tags/原理/" rel="tag"><i class="fa fa-tag"></i> 原理</a>
          
            <a href="/tags/redis/" rel="tag"><i class="fa fa-tag"></i> redis</a>
          
            <a href="/tags/主从复制/" rel="tag"><i class="fa fa-tag"></i> 主从复制</a>
          
        </div>
      

      
      
      

      
        <div class="post-nav">
          <div class="post-nav-next post-nav-item">
            
              <a href="/redis-skiplist-why/" rel="next" title="Redis为什么用跳表而不用平衡树？">
                <i class="fa fa-chevron-left"></i> Redis为什么用跳表而不用平衡树？
              </a>
            
          </div>

          <span class="post-nav-divider"></span>

          <div class="post-nav-prev post-nav-item">
            
              <a href="/mysql-install/" rel="prev" title="mysql5.7 安装教程">
                mysql5.7 安装教程 <i class="fa fa-chevron-right"></i>
              </a>
            
          </div>
        </div>
      

      
      
    </footer>
  </div>
  
  
  
  </article>



    <div class="post-spread">
      
    </div>
  </div>


          </div>
          


          

  
	<div id="gitalk-container"></div>

  

<script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
<!-- 展示广告 -->
<ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-3331353808689126" data-ad-slot="4663393885" data-ad-format="auto" data-full-width-responsive="true"></ins>
<script>
     (adsbygoogle = window.adsbygoogle || []).push({});
</script>


        </div>
        
          
  
  <div class="sidebar-toggle">
    <div class="sidebar-toggle-line-wrap">
      <span class="sidebar-toggle-line sidebar-toggle-line-first"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-middle"></span>
      <span class="sidebar-toggle-line sidebar-toggle-line-last"></span>
    </div>
  </div>

  <aside id="sidebar" class="sidebar">
    
    <div class="sidebar-inner">

      

      
        <ul class="sidebar-nav motion-element">
          <li class="sidebar-nav-toc sidebar-nav-active" data-target="post-toc-wrap">
            文章目录
          </li>
          <li class="sidebar-nav-overview" data-target="site-overview-wrap">
            站点概览
          </li>
        </ul>
      

      <section class="site-overview-wrap sidebar-panel">
        <div class="site-overview">
          <div class="site-author motion-element" itemprop="author" itemscope itemtype="http://schema.org/Person">
            
              <img class="site-author-image" itemprop="image" src="/images/avatar.jpg" alt="Big Jelly">
            
              <p class="site-author-name" itemprop="name">Big Jelly</p>
              <p class="site-description motion-element" itemprop="description">Nothing is easier than to deceive oneself</p>
          </div>

          <nav class="site-state motion-element">

            
              <div class="site-state-item site-state-posts">
              
                <a href="/archives/">
              
                  <span class="site-state-item-count">55</span>
                  <span class="site-state-item-name">日志</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-categories">
                <a href="/categories/index.html">
                  <span class="site-state-item-count">15</span>
                  <span class="site-state-item-name">分类</span>
                </a>
              </div>
            

            
              
              
              <div class="site-state-item site-state-tags">
                <a href="/tags/index.html">
                  <span class="site-state-item-count">68</span>
                  <span class="site-state-item-name">标签</span>
                </a>
              </div>
            

          </nav>

          
            <div class="feed-link motion-element">
              <a href="/atom.xml" rel="alternate">
                <i class="fa fa-rss"></i>
                RSS
              </a>
            </div>
          

          
            <div class="links-of-author motion-element">
                
                  <span class="links-of-author-item">
                    <a rel="external nofollow" href="https://github.com/jelly54" target="_blank" title="GitHub">
                      
                        <i class="fa fa-fw fa-github"></i>GitHub</a>
                  </span>
                
                  <span class="links-of-author-item">
                    <a rel="external nofollow" href="mailto:jelly_54@163.com" target="_blank" title="E-Mail">
                      
                        <i class="fa fa-fw fa-envelope"></i>E-Mail</a>
                  </span>
                
            </div>
          

          
          

          
          
            <div class="links-of-blogroll motion-element links-of-blogroll-inline">
              <div class="links-of-blogroll-title">
                <i class="fa  fa-fw fa-link"></i>
                外链小站
              </div>
              <ul class="links-of-blogroll-list">
                
                  <li class="links-of-blogroll-item">
                    <a href="https://leetcode.com/" title="LeetCode" rel="external nofollow" target="_blank">LeetCode</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://www.dooccn.com/" title="在线编辑代码" rel="external nofollow" target="_blank">在线编辑代码</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://www.chuangzaoshi.com/code" title="创造狮导航" rel="external nofollow" target="_blank">创造狮导航</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://24mail.chacuo.net/?utm_campaign=haruki&utm_content=note&utm_medium=reader_share&utm_source=qq" title="24Mail" rel="external nofollow" target="_blank">24Mail</a>
                  </li>
                
                  <li class="links-of-blogroll-item">
                    <a href="http://e.xitu.io/" title="掘金酱" rel="external nofollow" target="_blank">掘金酱</a>
                  </li>
                
              </ul>
            </div>
          

          

        </div>
      </section>

      
      <!--noindex-->
        <section class="post-toc-wrap motion-element sidebar-panel sidebar-panel-active">
          <div class="post-toc">

            
              
            

            
              <div class="post-toc-content"><ol class="nav"><li class="nav-item nav-level-1"><a class="nav-link" href="#Redis-是什么"><span class="nav-number">1.</span> <span class="nav-text">Redis 是什么</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#与其他key-value缓存产品区别？"><span class="nav-number">1.1.</span> <span class="nav-text">与其他key-value缓存产品区别？</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#五种数据类型"><span class="nav-number">2.</span> <span class="nav-text">五种数据类型</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis如何管理"><span class="nav-number">2.1.</span> <span class="nav-text">Redis如何管理</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#5种数据类型"><span class="nav-number">2.2.</span> <span class="nav-text">5种数据类型</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#数据类型应用场景总结"><span class="nav-number">2.3.</span> <span class="nav-text">数据类型应用场景总结</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Redis缓存"><span class="nav-number">3.</span> <span class="nav-text">Redis缓存</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#Tips"><span class="nav-number">3.1.</span> <span class="nav-text">Tips</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#RedisTemplate使用方式"><span class="nav-number">3.2.</span> <span class="nav-text">RedisTemplate使用方式</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#使用spring-cache集成redis"><span class="nav-number">3.3.</span> <span class="nav-text">使用spring cache集成redis</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#缓存注解"><span class="nav-number">3.4.</span> <span class="nav-text">缓存注解</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#Cacheable"><span class="nav-number">3.4.1.</span> <span class="nav-text">@Cacheable</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#CachePut"><span class="nav-number">3.4.2.</span> <span class="nav-text">@CachePut</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#CacheEvict"><span class="nav-number">3.4.3.</span> <span class="nav-text">@CacheEvict</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#缓存问题"><span class="nav-number">4.</span> <span class="nav-text">缓存问题</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#缓存和数据库数据一致性问题"><span class="nav-number">4.1.</span> <span class="nav-text">缓存和数据库数据一致性问题</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#缓存雪崩？"><span class="nav-number">4.2.</span> <span class="nav-text">缓存雪崩？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#怎么应对？"><span class="nav-number">4.2.1.</span> <span class="nav-text">怎么应对？</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#缓存穿透和击穿？"><span class="nav-number">4.3.</span> <span class="nav-text">缓存穿透和击穿？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#怎么解决？"><span class="nav-number">4.3.1.</span> <span class="nav-text">怎么解决？</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Redis为何这么快"><span class="nav-number">5.</span> <span class="nav-text">Redis为何这么快</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#redis作为缓存大家都在用，那redis一定很快咯？"><span class="nav-number">5.1.</span> <span class="nav-text">redis作为缓存大家都在用，那redis一定很快咯？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#redis这么快，它的“多线程模型”你了解吗？（露出邪魅一笑）"><span class="nav-number">5.2.</span> <span class="nav-text">redis这么快，它的“多线程模型”你了解吗？（露出邪魅一笑）</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis是单线程的，为什么还能这么快吗？"><span class="nav-number">5.3.</span> <span class="nav-text">Redis是单线程的，为什么还能这么快吗？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#Redis和Memcached的区别"><span class="nav-number">5.4.</span> <span class="nav-text">Redis和Memcached的区别</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#Redis单线程模型"><span class="nav-number">6.</span> <span class="nav-text">Redis单线程模型</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#文件事件处理器"><span class="nav-number">6.1.</span> <span class="nav-text">文件事件处理器</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#文件事件"><span class="nav-number">6.2.</span> <span class="nav-text">文件事件</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#文件事件处理器-1"><span class="nav-number">6.3.</span> <span class="nav-text">文件事件处理器</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#客户端与redis通信的一次流程"><span class="nav-number">6.4.</span> <span class="nav-text">客户端与redis通信的一次流程</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#过期策略"><span class="nav-number">7.</span> <span class="nav-text">过期策略</span></a></li><li class="nav-item nav-level-1"><a class="nav-link" href="#淘汰策略"><span class="nav-number">8.</span> <span class="nav-text">淘汰策略</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#手写lru算法"><span class="nav-number">8.1.</span> <span class="nav-text">手写lru算法</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#持久化"><span class="nav-number">9.</span> <span class="nav-text">持久化</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#redis的持久化机制？"><span class="nav-number">9.1.</span> <span class="nav-text">redis的持久化机制？</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#RDB是怎么工作的？"><span class="nav-number">9.2.</span> <span class="nav-text">RDB是怎么工作的？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#工作原理"><span class="nav-number">9.2.1.</span> <span class="nav-text">工作原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#RDB的优"><span class="nav-number">9.2.2.</span> <span class="nav-text">RDB的优</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#AOF是怎么工作的？"><span class="nav-number">9.3.</span> <span class="nav-text">AOF是怎么工作的？</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#工作原理-1"><span class="nav-number">9.3.1.</span> <span class="nav-text">工作原理</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#使用AOF的优点"><span class="nav-number">9.3.2.</span> <span class="nav-text">使用AOF的优点</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#该用哪一个呢？"><span class="nav-number">9.4.</span> <span class="nav-text">该用哪一个呢？</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#主从复制"><span class="nav-number">10.</span> <span class="nav-text">主从复制</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#redis-replication核心机制"><span class="nav-number">10.1.</span> <span class="nav-text">redis replication核心机制</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#master持久化对主从架构的意义"><span class="nav-number">10.2.</span> <span class="nav-text">master持久化对主从架构的意义</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#复制的过程"><span class="nav-number">10.3.</span> <span class="nav-text">复制的过程</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#数据同步的过程"><span class="nav-number">10.4.</span> <span class="nav-text">数据同步的过程</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#psync的执行流程："><span class="nav-number">10.4.1.</span> <span class="nav-text">psync的执行流程：</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#全量复制和部分复制"><span class="nav-number">10.5.</span> <span class="nav-text">全量复制和部分复制</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#全量复制的流程"><span class="nav-number">10.5.1.</span> <span class="nav-text">全量复制的流程</span></a></li><li class="nav-item nav-level-3"><a class="nav-link" href="#部分复制-增量复制"><span class="nav-number">10.5.2.</span> <span class="nav-text">部分复制(增量复制)</span></a></li></ol></li><li class="nav-item nav-level-2"><a class="nav-link" href="#心跳机制"><span class="nav-number">10.6.</span> <span class="nav-text">心跳机制</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#主从复制断点续传"><span class="nav-number">10.7.</span> <span class="nav-text">主从复制断点续传</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#无磁盘化复制"><span class="nav-number">10.8.</span> <span class="nav-text">无磁盘化复制</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#过期Key"><span class="nav-number">10.9.</span> <span class="nav-text">过期Key</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#主从复制会存在的问题"><span class="nav-number">10.10.</span> <span class="nav-text">主从复制会存在的问题</span></a><ol class="nav-child"><li class="nav-item nav-level-3"><a class="nav-link" href="#比较主流的解决方案？"><span class="nav-number">10.10.1.</span> <span class="nav-text">比较主流的解决方案？</span></a></li></ol></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#哨兵"><span class="nav-number">11.</span> <span class="nav-text">哨兵</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#哨兵的工作原理"><span class="nav-number">11.1.</span> <span class="nav-text">哨兵的工作原理</span></a></li></ol></li><li class="nav-item nav-level-1"><a class="nav-link" href="#生产环境中redis是怎么部署的"><span class="nav-number">12.</span> <span class="nav-text">生产环境中redis是怎么部署的</span></a><ol class="nav-child"><li class="nav-item nav-level-2"><a class="nav-link" href="#机器什么配置"><span class="nav-number">12.1.</span> <span class="nav-text">机器什么配置</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#redis-cluster"><span class="nav-number">12.2.</span> <span class="nav-text">redis cluster</span></a></li><li class="nav-item nav-level-2"><a class="nav-link" href="#内存中写的是什么数据？每条数据大小是多少？"><span class="nav-number">12.3.</span> <span class="nav-text">内存中写的是什么数据？每条数据大小是多少？</span></a></li></ol></li></ol></div>
            

          </div>
        </section>
      <!--/noindex-->
      

      

      <script async src="https://pagead2.googlesyndication.com/pagead/js/adsbygoogle.js"></script>
      <!-- 侧边栏广告 -->
      <ins class="adsbygoogle" style="display:block" data-ad-client="ca-pub-3331353808689126" data-ad-slot="7563237003" data-ad-format="auto" data-full-width-responsive="true"></ins>
      <script>
      	(adsbygoogle = window.adsbygoogle || []).push({});
      </script>
    </div>
  </aside>


        
      </div>
    </main>

    <footer id="footer" class="footer">
      <div class="footer-inner">
        <div class="copyright"> &copy; 2017 &mdash; <span itemprop="copyrightYear">2020</span>
  <span class="with-love">
    <i class="fa fa-heart"></i>
  </span>
  <span class="author" itemprop="copyrightHolder">Big Jelly</span>

</div>

<div class="powered-by">
	
		<span class="post-meta-item-icon">
			<i class="fa fa-area-chart"></i>
		</span>
		
			<span class="post-meta-item-text">Site words total count&#58;</span>
		
		<span title="Site words total count">140.8k</span>
	
  
	
		<script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

		<span class="post-meta-divider">|</span>
		<span class="post-visit-item-icon">
			<i class="fa fa-user-md"></i>
		</span>
		<span class="busuanzi_container_site_uv">
			Visitors to this site: <span id="busuanzi_value_site_uv"></span>
		</span>
	
  


	
</div>
        







        
      </div>
    </footer>

    
      <div class="back-to-top">
        <i class="fa fa-arrow-up"></i>
        
      </div>
    

    

  </div>

  

<script type="text/javascript">
  if (Object.prototype.toString.call(window.Promise) !== '[object Function]') {
    window.Promise = null;
  }
</script>









  


  











  
  
    <script type="text/javascript" src="/lib/jquery/index.js?v=2.1.3"></script>
  

  
  
    <script type="text/javascript" src="/lib/fastclick/lib/fastclick.min.js?v=1.0.6"></script>
  

  
  
    <script type="text/javascript" src="/lib/jquery_lazyload/jquery.lazyload.js?v=1.9.7"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/velocity/velocity.ui.min.js?v=1.2.1"></script>
  

  
  
    <script type="text/javascript" src="/lib/fancybox/source/jquery.fancybox.pack.js?v=2.1.5"></script>
  

  
  
    <script type="text/javascript" src="/lib/canvas-nest/canvas-nest.min.js"></script>
  


  


  <script type="text/javascript" src="/js/src/utils.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/motion.js?v=5.1.4"></script>



  
  


  <script type="text/javascript" src="/js/src/affix.js?v=5.1.4"></script>

  <script type="text/javascript" src="/js/src/schemes/pisces.js?v=5.1.4"></script>



  
  <script type="text/javascript" src="/js/src/scrollspy.js?v=5.1.4"></script>
<script type="text/javascript" src="/js/src/post-details.js?v=5.1.4"></script>



  


  <script type="text/javascript" src="/js/src/bootstrap.js?v=5.1.4"></script>



  


  




	





  





  












  <link rel="stylesheet" href="https://unpkg.com/gitalk/dist/gitalk.css">
  <script src="https://unpkg.com/gitalk/dist/gitalk.min.js"></script>
  <script src="/js/md5.min.js"></script>
   <script type="text/javascript">
        var gitalk = new Gitalk({
          clientID: 'ca31692554c3aea431f0',
          clientSecret: '1173c9e9c3c9038304c9a5b08929a359cae9f847',
          repo: 'jelly54.github.io',
          owner: 'jelly54',
          admin: ['jelly54'],
          id: md5(location.pathname),
          distractionFreeMode: 'true'
        })
        gitalk.render('gitalk-container')
       </script>

  

  <script type="text/javascript">
    // Popup Window;
    var isfetched = false;
    var isXml = true;
    // Search DB path;
    var search_path = "search.xml";
    if (search_path.length === 0) {
      search_path = "search.xml";
    } else if (/json$/i.test(search_path)) {
      isXml = false;
    }
    var path = "/" + search_path;
    // monitor main search box;

    var onPopupClose = function (e) {
      $('.popup').hide();
      $('#local-search-input').val('');
      $('.search-result-list').remove();
      $('#no-result').remove();
      $(".local-search-pop-overlay").remove();
      $('body').css('overflow', '');
    }

    function proceedsearch() {
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay"></div>')
        .css('overflow', 'hidden');
      $('.search-popup-overlay').click(onPopupClose);
      $('.popup').toggle();
      var $localSearchInput = $('#local-search-input');
      $localSearchInput.attr("autocapitalize", "none");
      $localSearchInput.attr("autocorrect", "off");
      $localSearchInput.focus();
    }

    // search function;
    var searchFunc = function(path, search_id, content_id) {
      'use strict';

      // start loading animation
      $("body")
        .append('<div class="search-popup-overlay local-search-pop-overlay">' +
          '<div id="search-loading-icon">' +
          '<i class="fa fa-spinner fa-pulse fa-5x fa-fw"></i>' +
          '</div>' +
          '</div>')
        .css('overflow', 'hidden');
      $("#search-loading-icon").css('margin', '20% auto 0 auto').css('text-align', 'center');

      $.ajax({
        url: path,
        dataType: isXml ? "xml" : "json",
        async: true,
        success: function(res) {
          // get the contents from search data
          isfetched = true;
          $('.popup').detach().appendTo('.header-inner');
          var datas = isXml ? $("entry", res).map(function() {
            return {
              title: $("title", this).text(),
              content: $("content",this).text(),
              url: $("url" , this).text()
            };
          }).get() : res;
          var input = document.getElementById(search_id);
          var resultContent = document.getElementById(content_id);
          var inputEventFunction = function() {
            var searchText = input.value.trim().toLowerCase();
            var keywords = searchText.split(/[\s\-]+/);
            if (keywords.length > 1) {
              keywords.push(searchText);
            }
            var resultItems = [];
            if (searchText.length > 0) {
              // perform local searching
              datas.forEach(function(data) {
                var isMatch = false;
                var hitCount = 0;
                var searchTextCount = 0;
                var title = data.title.trim();
                var titleInLowerCase = title.toLowerCase();
                var content = data.content.trim().replace(/<[^>]+>/g,"");
                var contentInLowerCase = content.toLowerCase();
                var articleUrl = decodeURIComponent(data.url);
                var indexOfTitle = [];
                var indexOfContent = [];
                // only match articles with not empty titles
                if(title != '') {
                  keywords.forEach(function(keyword) {
                    function getIndexByWord(word, text, caseSensitive) {
                      var wordLen = word.length;
                      if (wordLen === 0) {
                        return [];
                      }
                      var startPosition = 0, position = [], index = [];
                      if (!caseSensitive) {
                        text = text.toLowerCase();
                        word = word.toLowerCase();
                      }
                      while ((position = text.indexOf(word, startPosition)) > -1) {
                        index.push({position: position, word: word});
                        startPosition = position + wordLen;
                      }
                      return index;
                    }

                    indexOfTitle = indexOfTitle.concat(getIndexByWord(keyword, titleInLowerCase, false));
                    indexOfContent = indexOfContent.concat(getIndexByWord(keyword, contentInLowerCase, false));
                  });
                  if (indexOfTitle.length > 0 || indexOfContent.length > 0) {
                    isMatch = true;
                    hitCount = indexOfTitle.length + indexOfContent.length;
                  }
                }

                // show search results

                if (isMatch) {
                  // sort index by position of keyword

                  [indexOfTitle, indexOfContent].forEach(function (index) {
                    index.sort(function (itemLeft, itemRight) {
                      if (itemRight.position !== itemLeft.position) {
                        return itemRight.position - itemLeft.position;
                      } else {
                        return itemLeft.word.length - itemRight.word.length;
                      }
                    });
                  });

                  // merge hits into slices

                  function mergeIntoSlice(text, start, end, index) {
                    var item = index[index.length - 1];
                    var position = item.position;
                    var word = item.word;
                    var hits = [];
                    var searchTextCountInSlice = 0;
                    while (position + word.length <= end && index.length != 0) {
                      if (word === searchText) {
                        searchTextCountInSlice++;
                      }
                      hits.push({position: position, length: word.length});
                      var wordEnd = position + word.length;

                      // move to next position of hit

                      index.pop();
                      while (index.length != 0) {
                        item = index[index.length - 1];
                        position = item.position;
                        word = item.word;
                        if (wordEnd > position) {
                          index.pop();
                        } else {
                          break;
                        }
                      }
                    }
                    searchTextCount += searchTextCountInSlice;
                    return {
                      hits: hits,
                      start: start,
                      end: end,
                      searchTextCount: searchTextCountInSlice
                    };
                  }

                  var slicesOfTitle = [];
                  if (indexOfTitle.length != 0) {
                    slicesOfTitle.push(mergeIntoSlice(title, 0, title.length, indexOfTitle));
                  }

                  var slicesOfContent = [];
                  while (indexOfContent.length != 0) {
                    var item = indexOfContent[indexOfContent.length - 1];
                    var position = item.position;
                    var word = item.word;
                    // cut out 100 characters
                    var start = position - 20;
                    var end = position + 80;
                    if(start < 0){
                      start = 0;
                    }
                    if (end < position + word.length) {
                      end = position + word.length;
                    }
                    if(end > content.length){
                      end = content.length;
                    }
                    slicesOfContent.push(mergeIntoSlice(content, start, end, indexOfContent));
                  }

                  // sort slices in content by search text's count and hits' count

                  slicesOfContent.sort(function (sliceLeft, sliceRight) {
                    if (sliceLeft.searchTextCount !== sliceRight.searchTextCount) {
                      return sliceRight.searchTextCount - sliceLeft.searchTextCount;
                    } else if (sliceLeft.hits.length !== sliceRight.hits.length) {
                      return sliceRight.hits.length - sliceLeft.hits.length;
                    } else {
                      return sliceLeft.start - sliceRight.start;
                    }
                  });

                  // select top N slices in content

                  var upperBound = parseInt('1');
                  if (upperBound >= 0) {
                    slicesOfContent = slicesOfContent.slice(0, upperBound);
                  }

                  // highlight title and content

                  function highlightKeyword(text, slice) {
                    var result = '';
                    var prevEnd = slice.start;
                    slice.hits.forEach(function (hit) {
                      result += text.substring(prevEnd, hit.position);
                      var end = hit.position + hit.length;
                      result += '<b class="search-keyword">' + text.substring(hit.position, end) + '</b>';
                      prevEnd = end;
                    });
                    result += text.substring(prevEnd, slice.end);
                    return result;
                  }

                  var resultItem = '';

                  if (slicesOfTitle.length != 0) {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + highlightKeyword(title, slicesOfTitle[0]) + "</a>";
                  } else {
                    resultItem += "<li><a href='" + articleUrl + "' class='search-result-title'>" + title + "</a>";
                  }

                  slicesOfContent.forEach(function (slice) {
                    resultItem += "<a href='" + articleUrl + "'>" +
                      "<p class=\"search-result\">" + highlightKeyword(content, slice) +
                      "...</p>" + "</a>";
                  });

                  resultItem += "</li>";
                  resultItems.push({
                    item: resultItem,
                    searchTextCount: searchTextCount,
                    hitCount: hitCount,
                    id: resultItems.length
                  });
                }
              })
            };
            if (keywords.length === 1 && keywords[0] === "") {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-search fa-5x" /></div>'
            } else if (resultItems.length === 0) {
              resultContent.innerHTML = '<div id="no-result"><i class="fa fa-frown-o fa-5x" /></div>'
            } else {
              resultItems.sort(function (resultLeft, resultRight) {
                if (resultLeft.searchTextCount !== resultRight.searchTextCount) {
                  return resultRight.searchTextCount - resultLeft.searchTextCount;
                } else if (resultLeft.hitCount !== resultRight.hitCount) {
                  return resultRight.hitCount - resultLeft.hitCount;
                } else {
                  return resultRight.id - resultLeft.id;
                }
              });
              var searchResultList = '<ul class=\"search-result-list\">';
              resultItems.forEach(function (result) {
                searchResultList += result.item;
              })
              searchResultList += "</ul>";
              resultContent.innerHTML = searchResultList;
            }
          }

          if ('auto' === 'auto') {
            input.addEventListener('input', inputEventFunction);
          } else {
            $('.search-icon').click(inputEventFunction);
            input.addEventListener('keypress', function (event) {
              if (event.keyCode === 13) {
                inputEventFunction();
              }
            });
          }

          // remove loading animation
          $(".local-search-pop-overlay").remove();
          $('body').css('overflow', '');

          proceedsearch();
        }
      });
    }

    // handle and trigger popup window;
    $('.popup-trigger').click(function(e) {
      e.stopPropagation();
      if (isfetched === false) {
        searchFunc(path, 'local-search-input', 'local-search-result');
      } else {
        proceedsearch();
      };
    });

    $('.popup-btn-close').click(onPopupClose);
    $('.popup').click(function(e){
      e.stopPropagation();
    });
    $(document).on('keyup', function (event) {
      var shouldDismissSearchPopup = event.which === 27 &&
        $('.search-popup').is(':visible');
      if (shouldDismissSearchPopup) {
        onPopupClose();
      }
    });
  </script>





  

  

  

  
  

  

  

  



  
<script type="text/javascript" color="0,0,255" opacity="0.7" zindex="-2" count="99" src="//cdn.bootcss.com/canvas-nest.js/1.0.0/canvas-nest.min.js"></script>


<!-- 页面点击小红心 -->
<script type="text/javascript" src="/js/src/love.js"></script>
<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"log":true,"model":{"scale":1,"jsonPath":"/live2dw/assets/miku.model.json"},"display":{"position":"right","width":200,"height":400,"hOffset":0,"vOffset":-5},"mobile":{"show":true,"scale":0.5},"react":{"opacity":0.9},"pluginJsPath":"lib/","pluginModelPath":"assets/","pluginRootPath":"live2dw/","tagMode":false});</script></body>
</html>
